using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using MacomberMap.Common.Components;
using System.Xml;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.User_Interfaces.One_Lines.Elements;
using MacomberMap.Common.Internals;
using System.Reflection;
using MacomberMap.Common.Types;
using MacomberMap.Common.Database_Interface;
using System.Data.Common;
using System.Drawing.Printing;
using System.Drawing.Drawing2D;

namespace MacomberMap.Common.User_Interfaces.One_Lines
{
    /// <summary>
    /// This class provides the user interface for viewing one-lines
    /// </summary>
    public partial class MM_OneLine_Viewer : UserControl
    {
        #region Variable Declarations       
        /// <summary>Our collection of elements within the one-line</summary>
        public Dictionary<MM_Element, MM_OneLine_Element> DisplayElements = new Dictionary<MM_Element, MM_OneLine_Element>();

        /// <summary>The long name/descriptor of the contingency definition, if any</summary>
        public String ContingencyLongName;

        /// <summary>Our collection of nodes within the one-line</summary>
        public Dictionary<MM_Element, MM_OneLine_Node> DisplayNodes = new Dictionary<MM_Element, MM_OneLine_Node>();

        /// <summary>The poke points on the display</summary>
        public Dictionary<Point, MM_OneLine_PokePoint> PokePoints = new Dictionary<Point, MM_OneLine_PokePoint>();

        /// <summary>The collection of descriptors</summary>
        public Dictionary<MM_Element, MM_OneLine_Descriptor> Descriptors = new Dictionary<MM_Element, MM_OneLine_Descriptor>();

        /// <summary>The collection of secondary descriptors</summary>
        public Dictionary<MM_Element, MM_OneLine_SecondaryDescriptor> SecondaryDescriptors = new Dictionary<MM_Element, MM_OneLine_SecondaryDescriptor>();

        /// <summary>The collection of elements not linked to TEIDs</summary>
        public Dictionary<MM_OneLine_Element, bool> UnlinkedElements = new Dictionary<MM_OneLine_Element, bool>();

        /// <summary>The collection of transformer windings and their associated transformers</summary>
        public Dictionary<MM_Element, MM_OneLine_Transformer> TransformerWindings = new Dictionary<MM_Element, MM_OneLine_Transformer>(20);

        /// <summary>The base element of the view(substation or line)</summary>
        public MM_Element BaseElement
        {
            get { return DisplayParameters.BaseElement; }
            set { DisplayParameters.BaseElement = value; }
        }
        
        /// <summary>Whether this one-line is for a substation</summary>
        public bool IsSubstation
        {
            get { return BaseElement.ElemType.Name == "Substation"; }
        }


        /// <summary>
        /// The scrolling position of the panel
        /// </summary>
        public Point PanelScrollPosition
        {
            get
            {
                return new Point(-pnlElements.HorizontalScroll.Value, -pnlElements.VerticalScroll.Value);
                }
            set { 
                pnlElements.HorizontalScroll.Value = Math.Min(Math.Max(-value.X, pnlElements.HorizontalScroll.Minimum), pnlElements.HorizontalScroll.Maximum);
                pnlElements.VerticalScroll.Value = Math.Min(Math.Max(-value.Y, pnlElements.VerticalScroll.Minimum), pnlElements.VerticalScroll.Maximum);
                }
        }

        /// <summary>The controls in the panel</summary>
        public ControlCollection PanelControls
        {
            get { return pnlElements.Controls; }
        }

        /// <summary>The size of the panel</summary>
        public Size PanelDisplaySize
        {
            get { return pnlElements.DisplayRectangle.Size; }
        }

        /// <summary>The size of the panel</summary>
        public Size PanelSize
        {
            get { return pnlElements.Size; }
        }
        /// <summary>The display parameters for this view</summary>
        public MM_OneLine_Display DisplayParameters = new MM_OneLine_Display();

        /// <summary>An object that is being moved</summary>
        private List<MM_OneLine_Element> MovingObject = new List<MM_OneLine_Element>();

        /// <summary>The screen point where the mouse was first clicked down on</summary>
        private Point MouseDownPoint = new Point(-1, -1);
                       
        /// <summary>Handle clicking on the panel itself</summary>
        public event MouseEventHandler PanelClicked;

        /// <summary>Handle clicking on the panel itself</summary>
        public event MouseEventHandler PanelDoubleClicked;

        /// <summary>Handle clicking on the panel itself</summary>
        public event MouseEventHandler PanelMouseMove;        

        /// <summary>The database connector associated with the oneline</summary>
        public MM_Database DbConnection = null;

        /// <summary>
        /// The date/time this one-line was exported
        /// </summary>
        public DateTime ExportDate;

        /// <summary>
        /// The last CIM file that identified components in the one-line
        /// </summary>
        public string LastCIM;

        /// <summary>A rectangle for handling dragging</summary>
        public Rectangle DragRectangle = Rectangle.Empty;

        /// <summary>The old boundaries to allow for undoing</summary>
        private Dictionary<Control, Rectangle> OldBounds = new Dictionary<Control, Rectangle>();

        /// <summary>The collection of MM elements added by this one-line</summary>
        public Dictionary<UInt64, MM_Element> AddedElements = new Dictionary<UInt64, MM_Element>();
        #endregion

        #region Bounds handling
        /// <summary>
        /// Store all current boundaries
        /// </summary>
        private void StoreBounds()
        {
            OldBounds.Clear();
            foreach (Control ctl in pnlElements.Controls)
                OldBounds.Add(ctl, new Rectangle(ctl.Left - pnlElements.AutoScrollPosition.X, ctl.Top - pnlElements.AutoScrollPosition.Y, ctl.Width, ctl.Height));
                    
        }

        /// <summary>
        /// Restore the old boundaries
        /// </summary>
        private void RestoreBounds()
        {
            foreach (KeyValuePair<Control, Rectangle> kvp in OldBounds)
                kvp.Key.Bounds = new Rectangle(kvp.Value.Left + pnlElements.AutoScrollPosition.X, kvp.Value.Top + pnlElements.AutoScrollPosition.Y, kvp.Value.Width, kvp.Value.Height);
            foreach (MM_OneLine_Node NOB in DisplayNodes.Values)
                NOB.RebuildPaths(pnlElements.AutoScrollPosition);
            pnlElements.Refresh();
        }
        #endregion

        #region Element handling (addition/removal, etc)
        /// <summary>
        /// Add our hooks for new elements, etc.
        /// </summary>
        private void HookElementUpdators()
        {
            DisplayParameters.ElementsAdded += DisplayParameters_ElementsAdded;
            DisplayParameters.ElementsAssigned += DisplayParameters_ElementsAssigned;
            DisplayParameters.ElementsRemoved += DisplayParameters_ElementsRemoved;
            DisplayParameters.ElementsSelected += DisplayParameters_ElementsSelected;
            DisplayParameters.NodesAdded += DisplayParameters_NodesAdded;
            DisplayParameters.NodesRemoved += DisplayParameters_NodesRemoved;
            DisplayParameters.NodesSelected += DisplayParameters_NodesSelected;
        }

        private void DisplayParameters_NodesSelected(MM_OneLine_Node[] Nodes)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        /// <summary>
        /// Handle a node's removal
        /// </summary>
        /// <param name="Nodes"></param>
        private void DisplayParameters_NodesRemoved(MM_OneLine_Node[] Nodes)
        {
            foreach (MM_OneLine_Node Node in Nodes)
            {
                if (Node == null)
                    return;
                if (DisplayNodes.ContainsKey(Node.BaseElement))
                    DisplayNodes.Remove(Node.BaseElement);               
            }
        }

       
        /// <summary>
        /// Handle element selection on the one-line viewer
        /// </summary>
        /// <param name="Elements"></param>
        private void DisplayParameters_ElementsSelected(MM_OneLine_Element[] Elements)
        {
            
        }

        /// <summary>
        /// Handle element removal
        /// </summary>
        /// <param name="Elements"></param>
        private void DisplayParameters_ElementsRemoved(MM_OneLine_Element[] Elements)
        {
            foreach (MM_OneLine_Element Element in Elements)
                if (Element != null)
                {

                    //Add our element to our linked or unlinked elements
                    UnlinkedElements.Remove(Element);

                    if (Element.BaseElement != null  && (Element is MM_OneLine_Node || Element is MM_OneLine_PokePoint == false))
                        DisplayElements.Remove(Element.BaseElement);

                    if (Element is MM_OneLine_Transformer)
                        foreach (MM_OneLine_TransformerWinding Winding in (Element as MM_OneLine_Transformer).Windings)
                            if (Winding != null && Winding.BaseElement != null)
                                TransformerWindings.Remove(Winding.BaseElement);

                    if (Element.Descriptor != null && (Element is MM_OneLine_Node || Element is MM_OneLine_PokePoint == false))
                    {
                        if (Element.BaseElement != null)
                            Descriptors.Remove(Element.BaseElement);
                        pnlElements.Controls.Remove(Element.Descriptor);
                        pnlElements.Controls.Remove(Element.Descriptor);
                        Element.Descriptor.MouseDown -= Element_MouseDown;
                        Element.Descriptor.MouseMove -= Element_MouseMove;
                        Element.Descriptor.Dispose();
                    }

                    if (Element.SecondaryDescriptor != null && (Element is MM_OneLine_Node || Element is MM_OneLine_PokePoint == false))
                    {
                        if (Element.BaseElement != null)
                            SecondaryDescriptors.Remove(Element.BaseElement);
                        pnlElements.Controls.Remove(Element.SecondaryDescriptor);
                        pnlElements.Controls.Remove(Element.SecondaryDescriptor);
                        Element.SecondaryDescriptor.MouseDown -= Element_MouseDown;
                        Element.SecondaryDescriptor.MouseMove -= Element_MouseMove;
                        Element.SecondaryDescriptor.Dispose();
                    }


                    if (Element is MM_OneLine_SecondaryDescriptor && (Element as MM_OneLine_SecondaryDescriptor).ParentElement != null)
                    {
                        MM_OneLine_Element ParentElement = (Element as MM_OneLine_SecondaryDescriptor).ParentElement;
                        if (ParentElement.BaseElement != null)
                            SecondaryDescriptors.Remove(ParentElement.BaseElement);
                        ParentElement.SecondaryDescriptor = null;
                    }
                    else if (Element is MM_OneLine_Descriptor && (Element as MM_OneLine_Descriptor).ParentElement != null)
                    {
                        MM_OneLine_Element ParentElement = (Element as MM_OneLine_Descriptor).ParentElement;
                        if (ParentElement.BaseElement != null)
                            Descriptors.Remove(ParentElement.BaseElement);
                        ParentElement.Descriptor = null;
                    }

                    //Go through each node, make sure references to the element are removed
                    if (Element.BaseElement is MM_Node)
                        DisplayNodes.Remove(Element.BaseElement);
                    else
                        foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                            if (Node.ConnectionPoints.Remove(Element))
                                Node.RebuildPaths(pnlElements.AutoScrollPosition);


                    pnlElements.Controls.Remove(Element);
                    Element.MouseDown -= Element_MouseDown;
                    Element.MouseMove -= Element_MouseMove;
                    Element.MouseUp -= Element_MouseUp;
                    Element.Dispose();

                    //If the element is a poke point, go through nodes and buses to make sure that one isn't there
                    List<MM_OneLine_PokePoint> Pokes = new List<MM_OneLine_PokePoint>();
                    Dictionary<MM_OneLine_Element, MM_OneLine_PokePoint[]> ToUpdate = new Dictionary<MM_OneLine_Element, MM_OneLine_PokePoint[]>();
                    foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                    {
                        ToUpdate.Clear();
                        foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in Node.ConnectionPoints)
                        {
                            Pokes.Clear();
                            Pokes.AddRange(kvp.Value);
                            if (Pokes.Remove(Element as MM_OneLine_PokePoint))
                                ToUpdate.Add(kvp.Key, Pokes.ToArray());
                        }
                        foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in ToUpdate)
                            if (kvp.Value.Length == 0)
                                Node.ConnectionPoints.Remove(kvp.Key);
                            else
                                Node.ConnectionPoints[kvp.Key] = kvp.Value;

                        if (ToUpdate.Count > 0)
                            Node.RebuildPaths(pnlElements.AutoScrollPosition);
                    }
                }
        }

        /// <summary>
        /// Handle element assignment
        /// </summary>
        /// <param name="Elements"></param>
        private void DisplayParameters_ElementsAssigned(MM_OneLine_Element[] Elements)
        {
            foreach (MM_OneLine_Element Element in Elements)
                if (Element.BaseElement != null)
                {                    
                    if (DisplayElements.ContainsKey(Element.BaseElement))
                    {
                        if (DisplayElements[Element.BaseElement] != Element)                        
                            DisplayElements[Element.BaseElement]= null;                            
                        DisplayElements[Element.BaseElement] = Element;
                    }
                    else
                        DisplayElements.Add(Element.BaseElement, Element);
                    if (Element is MM_OneLine_Node && Element.BaseElement != null && !DisplayNodes.ContainsKey(Element.BaseElement))
                        DisplayNodes.Add(Element.BaseElement, Element as MM_OneLine_Node);
                    if (Element is MM_OneLine_Transformer)
                        foreach (MM_OneLine_TransformerWinding Winding in (Element as MM_OneLine_Transformer).Windings)
                            if (Winding != null && Winding.BaseElement != null && !TransformerWindings.ContainsKey(Winding.BaseElement))
                                TransformerWindings.Add(Winding.BaseElement, Element as MM_OneLine_Transformer);
                    if (Element.Descriptor != null && Element.BaseElement != null)
                        if (Descriptors.ContainsKey(Element.BaseElement) && Descriptors[Element.BaseElement] != Element.Descriptor)
                        {
                            pnlElements.Controls.Remove(Descriptors[Element.BaseElement]);
                            Descriptors[Element.BaseElement].Dispose();
                            Descriptors[Element.BaseElement] = Element.Descriptor;
                        }
                        else if (!Descriptors.ContainsKey(Element.BaseElement))
                        {
                            UnlinkedElements.Remove(Element.Descriptor);
                            Descriptors.Add(Element.BaseElement, Element.Descriptor);
                            pnlElements.Controls.Add(Element.Descriptor);
                        }

                    if (Element.SecondaryDescriptor != null)
                        Element.SecondaryDescriptor.RecomputeSize();
                    if (Element.SecondaryDescriptor != null && Element.BaseElement != null)
                        if (SecondaryDescriptors.ContainsKey(Element.BaseElement) && SecondaryDescriptors[Element.BaseElement] != Element.SecondaryDescriptor)
                        {
                            pnlElements.Controls.Remove(SecondaryDescriptors[Element.BaseElement]);
                            SecondaryDescriptors[Element.BaseElement].Dispose();
                            SecondaryDescriptors[Element.BaseElement] = Element.SecondaryDescriptor;
                        }
                        else if (!SecondaryDescriptors.ContainsKey(Element.BaseElement))
                        {
                            UnlinkedElements.Remove(Element.SecondaryDescriptor);
                            SecondaryDescriptors.Add(Element.BaseElement, Element.SecondaryDescriptor);
                            pnlElements.Controls.Add(Element.SecondaryDescriptor);
                        }
                    if (Element.SecondaryDescriptor != null)
                        Element.SecondaryDescriptor.RecomputeSize();
                    UnlinkedElements.Remove(Element);
                    Element.BringToFront();
                }
        }

        //TODO: remove test code.
        private void Element_LocationChanged(object sender, EventArgs e)
        {
            
        }

     

        /// <summary>
        /// Handle elements being added to the screen
        /// </summary>
        /// <param name="Elements"></param>
        private void DisplayParameters_ElementsAdded(MM_OneLine_Element[] Elements)
        {
            foreach (MM_OneLine_Element Element in Elements)
            {
                //Add our element to our linked or unlinked elements
                if (Element is MM_OneLine_UnlinkedElement)
                    UnlinkedElements.Add(Element, true);
                else if (Element is MM_OneLine_SecondaryDescriptor)
                {
                    MM_OneLine_SecondaryDescriptor SElement = Element as MM_OneLine_SecondaryDescriptor;
                    if (SElement.ParentElement.BaseElement == null)
                        UnlinkedElements.Add(SElement, false);
                    else if (SecondaryDescriptors.ContainsKey(SElement.ParentElement.BaseElement))
                    {
                        if (SecondaryDescriptors[SElement.ParentElement.BaseElement] != null && SecondaryDescriptors[SElement.ParentElement.BaseElement] != SElement)
                        {
                            pnlElements.Controls.Remove(SecondaryDescriptors[SElement.ParentElement.BaseElement]);
                            SecondaryDescriptors[SElement.ParentElement.BaseElement].Dispose();
                        }
                        SecondaryDescriptors[SElement.ParentElement.BaseElement] = SElement;
                    }
                    else
                        SecondaryDescriptors.Add(SElement.ParentElement.BaseElement, SElement );
                }
                else if (Element is MM_OneLine_Descriptor)
                {
                    MM_OneLine_Descriptor DElement = Element as MM_OneLine_Descriptor;
                    if (DElement.ParentElement.BaseElement == null)
                        UnlinkedElements.Add(DElement, false);
                    else if (Descriptors.ContainsKey(DElement.ParentElement.BaseElement))
                    {
                        if (Descriptors[DElement.ParentElement.BaseElement] != null && Descriptors[DElement.ParentElement.BaseElement] != DElement)
                        {
                            pnlElements.Controls.Remove(Descriptors[DElement.ParentElement.BaseElement]);
                            Descriptors[DElement.ParentElement.BaseElement].Dispose();
                        }
                        Descriptors[DElement.ParentElement.BaseElement] = DElement;
                    }
                    else
                        Descriptors.Add(DElement.ParentElement.BaseElement, DElement);
                }
                else if (Element.TEID == 0 || Element.BaseElement == null)
                    UnlinkedElements.Add(Element, false);
                else if (DisplayElements.ContainsKey(Element.BaseElement) && Element is MM_OneLine_PokePoint == false)
                    DisplayElements[Element.BaseElement] = Element;
                else if (!DisplayElements.ContainsKey(Element.BaseElement))
                    DisplayElements.Add(Element.BaseElement, Element);
                    /*if (Element is MM_OneLine_PricingVector)
                    DisplayElements.Add(Element.BaseElement, Element);
                else if (Element is MM_OneLine_PokePoint == false)
                    DisplayElements.Add(Element.BaseElement, Element);*/

                if (Element is MM_OneLine_Transformer)
                    foreach (MM_OneLine_TransformerWinding Winding in (Element as MM_OneLine_Transformer).Windings)
                        if (Winding != null && Winding.BaseElement != null && !TransformerWindings.ContainsKey(Winding.BaseElement))
                            TransformerWindings.Add(Winding.BaseElement, Element as MM_OneLine_Transformer);

                if (Element is MM_OneLine_Node && Element.BaseElement != null)
                    if (!DisplayNodes.ContainsKey(Element.BaseElement))
                        DisplayNodes.Add(Element.BaseElement, Element as MM_OneLine_Node);

                if (Element.SecondaryDescriptor != null)
                {
                    if (SecondaryDescriptors.ContainsKey(Element.BaseElement) && SecondaryDescriptors[Element.BaseElement] != Element.SecondaryDescriptor)
                    {
                        pnlElements.Controls.Remove(SecondaryDescriptors[Element.BaseElement]);
                        SecondaryDescriptors[Element.BaseElement].Dispose();
                        SecondaryDescriptors[Element.BaseElement] = Element.SecondaryDescriptor;
                    }
                    else if (!SecondaryDescriptors.ContainsKey(Element.BaseElement))
                        SecondaryDescriptors.Add(Element.BaseElement, Element.SecondaryDescriptor);
                }

                if (Element.Descriptor != null)
                {
                    if (Descriptors.ContainsKey(Element.BaseElement) && Descriptors[Element.BaseElement] != Element.Descriptor)
                    {
                        pnlElements.Controls.Remove(Descriptors[Element.BaseElement]);
                        Descriptors[Element.BaseElement].Dispose();
                        Descriptors[Element.BaseElement] = Element.Descriptor;
                    }
                    else if (!Descriptors.ContainsKey(Element.BaseElement))
                        Descriptors.Add(Element.BaseElement, Element.Descriptor);
                }
                


                if (!pnlElements.Contains(Element) && !Element.NotInOneLine)
                    pnlElements.Controls.Add(Element);


                //Make sure our mouse handler isn't already here
                Element.MouseDown -= Element_MouseDown;
                Element.MouseMove -= Element_MouseMove;
                Element.MouseUp -= Element_MouseUp;

                Element.MouseDown += Element_MouseDown;
                Element.MouseMove += Element_MouseMove;
                Element.MouseUp += Element_MouseUp;

                
                if (Element.Descriptor != null && !pnlElements.Contains(Element.Descriptor) && !Element.NotInOneLine)
                {
                    pnlElements.Controls.Add(Element.Descriptor);
                    Element.Descriptor.MouseDown -= Element_MouseDown;
                    Element.Descriptor.MouseMove -= Element_MouseMove;
                    Element.Descriptor.MouseUp -= Element_MouseUp;
                    Element.Descriptor.MouseDown += Element_MouseDown;
                    Element.Descriptor.MouseMove += Element_MouseMove;
                    Element.Descriptor.MouseUp += Element_MouseUp;
                }
                if (Element.SecondaryDescriptor != null && !pnlElements.Contains(Element.SecondaryDescriptor) && !Element.NotInOneLine)
                {
                    pnlElements.Controls.Add(Element.SecondaryDescriptor);
                    Element.SecondaryDescriptor.MouseDown -= Element_MouseDown;
                    Element.SecondaryDescriptor.MouseMove -= Element_MouseMove;
                    Element.SecondaryDescriptor.MouseUp -= Element_MouseUp;
                    Element.SecondaryDescriptor.MouseDown += Element_MouseDown;
                    Element.SecondaryDescriptor.MouseMove += Element_MouseMove;
                    Element.SecondaryDescriptor.MouseUp += Element_MouseUp;
                }

            }
        }

        /// <summary>
        /// Handle nodes being added to the screen
        /// </summary>
        /// <param name="Nodes"></param>
        private void DisplayParameters_NodesAdded(MM_OneLine_Node[] Nodes)
        {
            foreach (MM_OneLine_Node Node in Nodes)
            {
                if (Node == null)
                    return;
                if (DisplayNodes.ContainsKey(Node.BaseElement))
                    DisplayNodes.Remove(Node.BaseElement);


                DisplayNodes.Add(Node.BaseElement, Node);
                if (!DisplayElements.ContainsKey(Node.BaseElement))
                    DisplayElements.Add(Node.BaseElement, Node as MM_OneLine_Node);
                CheckNodeConnections(Node);

                //Make sure all poke points are in the display
                foreach (MM_OneLine_PokePoint[] Pokes in Node.ConnectionPoints.Values)
                    foreach (MM_OneLine_PokePoint Poke in Pokes)
                        if (Poke.Parent == null)
                            pnlElements.Controls.Add(Poke);

                //Make sure our mouse handler isn't already here
                Node.MouseDown -= Element_MouseDown;
                Node.MouseMove -= Element_MouseMove;
                Node.MouseUp -= Element_MouseUp;

                Node.MouseDown += Element_MouseDown;
                Node.MouseMove += Element_MouseMove;
                Node.MouseUp += Element_MouseUp;
            }
        }

        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new one-line viewer
        /// </summary>                
        public MM_OneLine_Viewer()
        {
            InitializeComponent();
            HookElementUpdators();         
        }



        /// <summary>
        /// Handle a control being added by recalculating the size
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_ControlAdded(object sender, ControlEventArgs e)
        {
            pnlElements.HorizontalScroll.Maximum = Math.Max(e.Control.Right, pnlElements.Width);
            pnlElements.VerticalScroll.Maximum = Math.Max(e.Control.Right, pnlElements.Height);
            pnlElements.HorizontalScroll.LargeChange = pnlElements.HorizontalScroll.Maximum / 10;
            pnlElements.VerticalScroll.LargeChange = pnlElements.VerticalScroll.Maximum / 10;
            Console.WriteLine("Control added: {0} ({1}, {2})", e.Control.ToString(), e.Control.Right, e.Control.Bottom);
        }

                             
       

        /// <summary>
        /// Initialize a new one-line viewer
        /// </summary>
        /// <param name="OneLineXML">The XML document containing the parameters for the one-line</param>
        /// <param name="DbConnection">The database providing element lookup</param>
        public MM_OneLine_Viewer(XmlElement OneLineXML, MM_Database DbConnection)
        {
            InitializeComponent();            
            LoadXml(OneLineXML);
            this.AutoScroll = true;
            this.DbConnection = DbConnection;
            HookElementUpdators();
            
        }

        /// <summary>
        /// Assign a database connection to the oneline viewer
        /// </summary>
        /// <param name="DbConnection">The database providing element lookup</param>
        public void AssignConnection(MM_Database DbConnection)
        {
            this.DbConnection = DbConnection;
        }

        /// <summary>
        /// Load an Macomber Map One-line
        /// </summary>
        /// <param name="BaseXML"></param>        
        public void LoadXml(XmlElement BaseXML)
        {
            DisplayElements.Clear();
            pnlElements.Controls.Clear();
            UnlinkedElements.Clear();
            
            //Pull in our header data                       
            this.ExportDate = XmlConvert.ToDateTime(BaseXML.Attributes["ExportDate"].Value,   XmlDateTimeSerializationMode.Unspecified);
            this.LastCIM = BaseXML.Attributes["LastCIM"].Value;
            if (BaseXML.HasAttribute("ContingencyLongName"))
                this.ContingencyLongName = BaseXML.Attributes["ContingencyLongName"].Value;

            //Read all attributes for the one-line viewer
            MM_Serializable.ReadXml(BaseXML, this);


            List<MM_OneLine_Element> OutElems = new List<MM_OneLine_Element>();
            List<MM_OneLine_Node> OutNodes = new List<MM_OneLine_Node>();
    
            //Now, pull in all one-line elements from the XML
            foreach (XmlElement xE in BaseXML["Elements"].ChildNodes)                        
                OutElems.Add(MM_OneLine_Element.CreateElement(xE, DisplayParameters, DbConnection, DisplayElements, this));                

            //Run through our elements, make sure the descriptors are accounted for
            int Tally = OutElems.Count;
            for (int a = 0; a < Tally; a++)
                if (OutElems[a].Descriptor != null)
                    OutElems.Add(OutElems[a].Descriptor);

            //Ensure our element are added, and reset.
            DisplayParameters.HandleAddedElements(OutElems.ToArray());
            OutElems.Clear();


            //Now, pull in all of the nodes            
             foreach (XmlElement xE in BaseXML["Nodes"].ChildNodes)                        
             {
                 MM_OneLine_Node NewElement = MM_OneLine_Element.CreateElement(xE, DisplayParameters, DbConnection, DisplayElements, this) as MM_OneLine_Node;
                 OutElems.Add(NewElement);
                 OutNodes.Add(NewElement);
            }                                      
           
            //Run through our nodes, and make sure all of the poke points are accounted for
            foreach (MM_OneLine_Node Node in OutNodes)
                foreach (MM_OneLine_PokePoint[] Pokes in Node.ConnectionPoints.Values)
                    foreach (MM_OneLine_PokePoint Poke in Pokes)
                        if (!OutElems.Contains(Poke))
                            OutElems.Add(Poke);

            foreach (XmlElement xE in BaseXML["Unlinked_Elements"])
                OutElems.Add(new MM_OneLine_UnlinkedElement(xE, DisplayParameters, DbConnection));

            //Run through our elements, make sure the descriptors are accounted for
            Tally = OutElems.Count;
            for (int a = 0; a < Tally; a++)
                if (OutElems[a].Descriptor != null)
                    OutElems.Add(OutElems[a].Descriptor);


            //Now, send our new elements and nodes out to the display
            DisplayParameters.HandleAddedElements(OutElems.ToArray());
            DisplayParameters.HandleAddedNodes(OutNodes.ToArray());

            //Bring all jumpers to the front
            foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                foreach (MM_OneLine_PokePoint[] Pokes in Node.ConnectionPoints.Values)
                    foreach (MM_OneLine_PokePoint Poke in Pokes)
                        if (Poke.IsJumper)
                            Poke.SendToBack();
        }

        /// <summary>
        /// Clear all elements within the one-line viewer
        /// </summary>
        public void ClearElements()
        {
            MM_OneLine_Element[] Elems = new MM_OneLine_Element[DisplayElements.Count];
            DisplayElements.Values.CopyTo(Elems, 0);
            DisplayParameters.HandleRemovedElements(Elems);
            pnlElements.Controls.Clear();
            DisplayElements.Clear();
            UnlinkedElements.Clear();
            Descriptors.Clear();
            SecondaryDescriptors.Clear();
            DisplayNodes.Clear();
            PokePoints.Clear();
            MovingObject.Clear();
        }

        /// <summary>
        /// Assign a base element to the one-line display
        /// </summary>
        /// <param name="BaseElement"></param>
        public void AssignBaseElement(MM_Element BaseElement)
        {
            this.BaseElement = BaseElement;            
        }

      



        /// <summary>
        /// Unassign an element that has received an associated element
        /// </summary>
        /// <param name="Element"></param>
        public void UnassignElement(MM_OneLine_Element Element)
        {
            if (DisplayElements.ContainsKey(Element.BaseElement))
                DisplayElements.Remove(Element.BaseElement);
            
                Descriptors.Remove(Element.BaseElement);
                SecondaryDescriptors.Remove(Element.BaseElement);

            if (!UnlinkedElements.ContainsKey(Element))
                UnlinkedElements.Add(Element,false);
        }


      
      

        /// <summary>
        /// Draw an arrow between two points
        /// </summary>
        /// <param name="SourceRect">The starting rectangle</param>
        /// <param name="TargetRect">The target rectangle</param>
        /// <param name="g">The graphics connection</param>
        /// <param name="ArrowEnd">Whether an arrow should appear at the end</param>
        private void DrawArrow(Rectangle SourceRect, Rectangle TargetRect, Graphics g, bool ArrowEnd)
        {
            Point StartPoint = new Point(SourceRect.X + (SourceRect.Width / 2), SourceRect.Y + (SourceRect.Height / 2));
            Point EndPoint = new Point(TargetRect.X + (TargetRect.Width / 2), TargetRect.Y + (TargetRect.Height / 2));

            if (TargetRect.Top > SourceRect.Bottom)
                EndPoint.Y = TargetRect.Top;
            else if (SourceRect.Top > TargetRect.Bottom)
                EndPoint.Y = TargetRect.Bottom;
            if (TargetRect.Left > SourceRect.Right)
                EndPoint.X = TargetRect.Left;
            else if (SourceRect.Left > TargetRect.Right)
                EndPoint.X = TargetRect.Right;

            if (SourceRect.Top > TargetRect.Bottom)
                StartPoint.Y = SourceRect.Top;
            else if (TargetRect.Top > SourceRect.Bottom)
                StartPoint.Y = SourceRect.Bottom;
            if (SourceRect.Left > TargetRect.Right)
                StartPoint.X = SourceRect.Left;
            else if (TargetRect.Left > SourceRect.Right)
                StartPoint.X = SourceRect.Right;
            
            using (Pen OutPen = new Pen(Color.DarkGray))
            {
                OutPen.StartCap = (ArrowEnd ? LineCap.Round : LineCap.ArrowAnchor);
                OutPen.EndCap = LineCap.ArrowAnchor;                                        
                OutPen.MiterLimit = 5;
                OutPen.LineJoin = LineJoin.Round;
                

                g.DrawLine(OutPen, StartPoint, EndPoint);
            }
        }

        
        /// <summary>
        /// Handle mouse movement over an element for dragging
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Element_MouseMove(object sender, MouseEventArgs e)
        {            
            MM_OneLine_Element Sender = sender as MM_OneLine_Element;                
            if (e.Button == MouseButtons.Left && MouseDownPoint.X > -1 && MovingObject != null && (Control.ModifierKeys & Keys.Shift) == Keys.None)
            {
                Point Delta = new Point(e.X - MouseDownPoint.X, e.Y - MouseDownPoint.Y);
                foreach (MM_OneLine_Element MovingObj in MovingObject)                
                    MoveElement(MovingObj, Delta);
                this.Refresh();
            }
            if (PanelMouseMove != null)
                PanelMouseMove(sender, new MouseEventArgs(e.Button, e.Clicks, e.X + Sender.Left  - pnlElements.AutoScrollPosition.X, e.Y + Sender.Top               - pnlElements.AutoScrollPosition.Y, e.Delta));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Element_MouseUp(object sender, MouseEventArgs e)
        {
            MM_OneLine_Element Sender = (MM_OneLine_Element)sender;
            if (e.Button == MouseButtons.Left && (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
                if (MovingObject.Contains(Sender))
                    MovingObject.Remove(Sender);
                else
                    MovingObject.Add(Sender);
            
            pnlElements.Refresh();
        }

        /// <summary>
        /// Move an element on the screen
        /// </summary>
        /// <param name="ElementToMove">The element to be moved</param>
        /// <param name="DeltaPoint">The coordinates by which it should be shifted</param>
        private void MoveElement(Control ElementToMove, Point DeltaPoint)
        {
            //Move the element we've been passed
            ElementToMove.Location = new Point(ElementToMove.Location.X + DeltaPoint.X, ElementToMove.Location.Y + DeltaPoint.Y);
                        
            //If we're holding down control, don't try and move subelements
            if ((Control.ModifierKeys & Keys.Control) == Keys.None)
            {
                //If the element has a text label, move it
                if (ElementToMove is MM_OneLine_Element && ((MM_OneLine_Element)ElementToMove).Descriptor != null)
                    MoveElement((ElementToMove as MM_OneLine_Element).Descriptor, DeltaPoint);
                if (ElementToMove is MM_OneLine_Element && ((MM_OneLine_Element)ElementToMove).SecondaryDescriptor != null)
                    MoveElement((ElementToMove as MM_OneLine_Element).SecondaryDescriptor, DeltaPoint);                
            }

            if (ElementToMove is MM_OneLine_PokePoint)
            {                
                MM_OneLine_Node BaseNode = ElementToMove is MM_OneLine_Node ? ElementToMove as MM_OneLine_Node : (ElementToMove as MM_OneLine_PokePoint).BaseNode;
                if (BaseNode != null)
                    BaseNode.RebuildPaths(pnlElements.AutoScrollPosition);                
            }
            else
                foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                    if (Node.ConnectionPoints.ContainsKey(ElementToMove as MM_OneLine_Element))
                        Node.RebuildPaths(pnlElements.AutoScrollPosition);                              
        }

        /// <summary>
        /// Select an element on the UI
        /// </summary>
        /// <param name="Elems">The elements to be selected</param>
        public void SelectElements(params MM_Element[] Elems)
        {
            MovingObject.Clear();
            foreach (MM_Element Elem in Elems)
                if (DisplayElements.ContainsKey(Elem))
                    MovingObject.Add(DisplayElements[Elem]);            
            pnlElements.Refresh();
        }

      

        /// <summary>
        /// Handle mouse pressing on an element for dragging
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Element_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && DisplayParameters.EditorMode && (Control.ModifierKeys & Keys.Shift) == Keys.None)
            {


                DragRectangle = Rectangle.Empty;
                MM_OneLine_Element Sender = sender as MM_OneLine_Element;
                MouseDownPoint = e.Location;
                if (!MovingObject.Contains(Sender))
                {
                    MovingObject.Clear();
                    MovingObject.Add(Sender);
                //    (sender as Control).BringToFront();
                }

                this.Refresh();
                pnlElements.Focus();
                DisplayParameters.HandleSelectedElements(MovingObject.ToArray());
            }
            else if (!DisplayParameters.EditorMode)
            {
                MovingObject.Clear();
                this.Refresh();
            }
        }

     

        /// <summary>
        /// Handle an item's dragging into the current window
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_DragEnter(object sender, DragEventArgs e)
        {
            Object InObject = e.Data.GetData(e.Data.GetFormats()[0]);
            if (DisplayParameters.EditorMode && (InObject is MM_OneLine_Element || InObject is MM_Element || InObject is MM_Element[]))
                e.Effect = DragDropEffects.All;
            else
                base.OnDragEnter(e);
            if (PanelMouseMove != null)
                PanelMouseMove(this, new MouseEventArgs(MouseButtons.Left, 0, e.X -  pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, 0));
        }

        /// <summary>
        /// Handle an item's drag/drop
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_DragDrop(object sender, DragEventArgs e)
        {
            try
            {
                List<MM_OneLine_Element> NewElems = new List<MM_OneLine_Element>();
                List<MM_OneLine_Node> NewNodes = new List<MM_OneLine_Node>();
                Object InObject = e.Data.GetData(e.Data.GetFormats()[0]);
                Point DeltaLocation = pnlElements.PointToClient(new Point(e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y));
                MovingObject.Clear();
                if (DisplayParameters.EditorMode && InObject is MM_OneLine_Element)
                    MovingObject.Add(InObject as MM_OneLine_Element);
                else if (DisplayParameters.EditorMode && InObject is MM_Element)
                    MovingObject.Add(MM_OneLine_Element.CreateElement(InObject as MM_Element, DisplayParameters, DisplayElements, MM_OneLine_Element.enumOrientations.Unknown));
                else if (DisplayParameters.EditorMode && InObject is MM_Element[])
                    foreach (MM_Element DropElem in InObject as MM_Element[])
                    {
                        MM_Element Elem = (DropElem is MM_BusbarSection ? (DropElem as MM_BusbarSection).AssociatedNode : DropElem);
                        if (DisplayElements.ContainsKey(Elem))
                        {
                            MovingObject.Add(DisplayElements[Elem]);
                            DisplayElements[Elem].Location = pnlElements.PointToClient(new Point(e.X, e.Y));
                            if (DisplayElements[Elem].Descriptor != null)
                                DisplayElements[Elem].Descriptor.Location = Point.Add(DeltaLocation, DisplayElements[Elem].Size);
                            /*if (!pnlElements.Controls.Contains(DisplayElements[Elem]))
                                DisplayParameters.HandleAddedElements(new MM_OneLine_Element[] { DisplayElements[Elem] });
                            CheckNodeConnections(DisplayElements[Elem]);*/
                        }
                        else if (Elem.ElemType.Name == "TransformerWinding")
                        {
                        }
                        else if (Elem is MM_Node)
                        {                            
                            MM_OneLine_Node NewNode = new MM_OneLine_Node(Elem as MM_Node, DisplayParameters, MM_OneLine_Element.enumOrientations.Unknown);
                            NewNode.Location = pnlElements.PointToClient(new Point(e.X, e.Y));
                            NewNode.Size = new Size(8, 8);
                            NewNodes.Add(NewNode);
                            NewElems.Add(NewNode);
                            foreach (MM_OneLine_PokePoint[] PokeCollection in NewNode.ConnectionPoints.Values)
                                foreach (MM_OneLine_PokePoint Poke in PokeCollection)
                                    NewElems.Add(Poke);
                        }                        
                        else if (Elem is MM_PricingVector)
                        {
                            MM_PricingVector PV = Elem as MM_PricingVector;
                            MM_Element TargetElem = PV.OtherElement;
                            if (TargetElem.ElemType.Name == "TransformerWinding" && TransformerWindings.ContainsKey(TargetElem))
                                TargetElem = TransformerWindings[TargetElem].BaseElement;

                            if (!DisplayElements.ContainsKey(TargetElem))
                                MessageBox.Show("Unable to add pricing vector - add " + TargetElem.ElemType.Name + " " + TargetElem.Name + " first.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                            else if (!DisplayNodes.ContainsKey(PV.NodeElement))
                                MessageBox.Show("Unable to add pricing vector - add " + PV.NodeElement.ElemType.Name + " " + PV.NodeElement.Name + " first.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
                            else
                            {
                                MM_OneLine_Node PVNode = DisplayNodes[PV.NodeElement];
                                MM_OneLine_Element PVElem = DisplayElements[TargetElem];
                                MM_OneLine_PricingVector FoundPV = null;
                                if (!PVNode.ConnectionPoints.ContainsKey(PVElem))
                                {
                                    MM_OneLine_PokePoint NewPoke = new MM_OneLine_PokePoint(PVNode.BaseElement, PVNode, DisplayParameters, MM_OneLine_Element.enumOrientations.Unknown, false, true);
                                    FoundPV = new MM_OneLine_PricingVector(PV, PVNode, DisplayParameters, MM_OneLine_Element.enumOrientations.Unknown);
                                    PVNode.ConnectionPoints.Add(PVElem, new MM_OneLine_PokePoint[] { NewPoke, FoundPV });
                                }
                                else
                                {
                                    foreach (MM_OneLine_PokePoint Poke in PVNode.ConnectionPoints[PVElem])
                                        if (FoundPV == null && Poke is MM_OneLine_PricingVector && (Poke as MM_OneLine_PricingVector).BaseElement == PV)
                                            FoundPV = Poke as MM_OneLine_PricingVector;

                                    if (FoundPV == null)
                                    {
                                        Point DeltaPt = new Point(Math.Min(e.X - PVElem.Left, e.X - PVElem.Right), Math.Min(e.Y - PVElem.Top, e.Y - PVElem.Bottom));
                                        MM_OneLine_Element.enumOrientations Orientation;
                                        if (Math.Abs(DeltaPt.X) > Math.Abs(DeltaPt.Y))
                                            Orientation = (DeltaPt.X > 0 ? MM_OneLine_Element.enumOrientations.Left : MM_OneLine_Element.enumOrientations.Right);
                                        else
                                            Orientation = (DeltaPt.Y > 0 ? MM_OneLine_Element.enumOrientations.Up : MM_OneLine_Element.enumOrientations.Down);
                                        FoundPV = new MM_OneLine_PricingVector(PV, PVNode, DisplayParameters, Orientation);
                                        List<MM_OneLine_PokePoint> Pokes = new List<MM_OneLine_PokePoint>(PVNode.ConnectionPoints[PVElem]);
                                        Pokes.Add(FoundPV);
                                        PVNode.ConnectionPoints[PVElem] = Pokes.ToArray();
                                        NewElems.Add(FoundPV);
                                    }
                                    else if (!DisplayElements.ContainsKey(PVElem.BaseElement))
                                        NewElems.Add(FoundPV);

                                }
                                if (FoundPV.Descriptor == null)
                                    FoundPV.Descriptor = new MM_OneLine_Descriptor(FoundPV, DisplayParameters);

                                FoundPV.Descriptor.Location = Point.Add(DeltaLocation, FoundPV.Size);
                                FoundPV.Location = DeltaLocation;
                            }
                        }
                        else
                        {
                            MM_OneLine_Element NewElem = MM_OneLine_Element.CreateElement(Elem, DisplayParameters, DisplayElements, MM_OneLine_Element.enumOrientations.Unknown);
                            NewElem.Descriptor = new MM_OneLine_Descriptor(NewElem, DisplayParameters);
                            MovingObject.Add(NewElem);
                            NewElem.Location = DeltaLocation;
                            NewElem.Descriptor.RecomputeSize();
                            Point Center = MM_OneLine_Element.CenterRect(NewElem.Descriptor.DisplayRectangle);

                            NewElem.Descriptor.Location = new Point(DeltaLocation.X - Center.X / 2, NewElem.Bottom + 2);
                            NewElems.Add(NewElem);
                        }
                    }
                DisplayParameters.HandleAddedElements(NewElems.ToArray());
                DisplayParameters.HandleAddedNodes(NewNodes.ToArray());
                foreach (MM_OneLine_Element Elem in MovingObject)
                    CheckNodeConnections(Elem);
                foreach (MM_OneLine_Element NewElem in NewElems)
                    CheckNodeConnections(NewElem);
                foreach (MM_OneLine_Node NewNode in NewNodes)
                    NewNode.RebuildPaths(pnlElements.AutoScrollPosition);
                pnlElements.Refresh();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error in drag/drop: " + ex.Message + "\n" + ex.StackTrace, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }


        /// <summary>
        /// When a new element is added, check to see whether the element is connected to other nodes, or the node to other elements
        /// </summary>
        /// <param name="NewElem"></param>
        public void CheckNodeConnections(MM_OneLine_Element NewElem)
        {
            
            //If we have a poke point, create connections between all elements
            if (NewElem is MM_OneLine_PokePoint)
            {
                MM_OneLine_Node Owner = NewElem is MM_OneLine_Node ? NewElem as MM_OneLine_Node : (NewElem as MM_OneLine_PokePoint).BaseNode;
                foreach (MM_Element LinkedElem in (Owner.BaseElement as MM_Node).ConnectedElements)
                    if (LinkedElem.ElemType.Name == "PricingVector")
                    { }
                    else if (DisplayElements.ContainsKey(LinkedElem) && !Owner.ConnectionPoints.ContainsKey(DisplayElements[LinkedElem]))
                        Owner.ConnectionPoints.Add(DisplayElements[LinkedElem], new MM_OneLine_PokePoint[] { NewElem as MM_OneLine_PokePoint });
                    else if (LinkedElem.ElemType.Name == "TransformerWinding" && TransformerWindings.ContainsKey(LinkedElem) && !Owner.ConnectionPoints.ContainsKey(TransformerWindings[LinkedElem]))
                        if (TransformerWindings.ContainsKey(LinkedElem))
                            Owner.ConnectionPoints.Add(TransformerWindings[LinkedElem], new MM_OneLine_PokePoint[] { NewElem as MM_OneLine_PokePoint });                        
                            
                //Now go back and handle the pricing vector
                foreach (MM_Element LinkedElem in (Owner.BaseElement as MM_Node).ConnectedElements)
                    if (LinkedElem.ElemType.Name == "PricingVector" && DisplayElements.ContainsKey((LinkedElem as MM_PricingVector).OtherElement) && DisplayElements.ContainsKey(LinkedElem))
                    {
                        List<MM_OneLine_PokePoint> Pokes = new List<MM_OneLine_PokePoint>(Owner.ConnectionPoints[DisplayElements[(LinkedElem as MM_PricingVector).OtherElement]]);
                        Pokes.Add(DisplayElements[LinkedElem] as MM_OneLine_PricingVector);                        
                        Owner.ConnectionPoints[DisplayElements[(LinkedElem as MM_PricingVector).OtherElement]] = Pokes.ToArray();
                        (DisplayElements[LinkedElem] as MM_OneLine_PricingVector).BaseNode = (NewElem as MM_OneLine_PokePoint).BaseNode;
                    }

                Owner.RebuildPaths(pnlElements.AutoScrollPosition);                               
            }          
            //Otherwise, if we have an element, go through that accordingly
            else
                foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                {
                    bool AddedNode = false;
                    foreach (MM_Element ConnectedElement in (Node.BaseElement as MM_Node).ConnectedElements)
                        if (ConnectedElement == NewElem.BaseElement && DisplayElements.ContainsKey(NewElem.BaseElement) && !Node.ConnectionPoints.ContainsKey(DisplayElements[NewElem.BaseElement]))
                        {
                            //First, figure out the average position of all the already-in-place elements (including the new one)
                            KeyValuePair<double, MM_OneLine_PokePoint> closest = new KeyValuePair<double, MM_OneLine_PokePoint>(double.NaN, null);
                            Point AggPoint = NewElem.Location;
                            Point CenterConnect = MM_OneLine_Element.CenterRect(NewElem.Bounds);

                            foreach (MM_OneLine_Element Elem in Node.ConnectionPoints.Keys)
                            {
                                AggPoint.X += Elem.Location.X;
                                AggPoint.Y += Elem.Location.Y;

                                //Look through the connection points to try and find the closest one
                                foreach (MM_OneLine_PokePoint Poke in Node.ConnectionPoints[Elem])
                                    if (Poke is MM_OneLine_PricingVector == false && Poke.IsVisible && !Poke.IsJumper)
                                    {
                                        Point PokeCenter = MM_OneLine_Element.CenterRect(Poke.Bounds);
                                        double Dist = Math.Sqrt(Math.Pow(PokeCenter.X - CenterConnect.X, 2) + Math.Pow(PokeCenter.Y - CenterConnect.Y, 2));
                                        if (double.IsNaN(closest.Key) || Dist < closest.Key)
                                            closest = new KeyValuePair<double, MM_OneLine_PokePoint>(Dist, Poke);
                                    }

                            }
                            AggPoint.X /= (Node.ConnectionPoints.Count + 1);
                            AggPoint.Y /= (Node.ConnectionPoints.Count + 1);
                            AddedNode = true;
                            //Now, create our new point   
                            if (double.IsNaN(closest.Key))
                                Node.ConnectionPoints.Add(NewElem, new MM_OneLine_PokePoint[] { LocatePoke(new Rectangle(AggPoint.X - 4, AggPoint.Y - 4, 8, 8), Node, DisplayParameters, Node.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, true) });
                            else
                                Node.ConnectionPoints.Add(NewElem, new MM_OneLine_PokePoint[] { closest.Value });
                        }
                    if (AddedNode)
                        Node.RebuildPaths(pnlElements.AutoScrollPosition);
                }
        }

        /// <summary>
        /// Handle the user's key presses to detect arrow movement of the selected one-line element
        /// </summary>
        /// <param name="msg"></param>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            //First, remove the control key
            Keys ThisKey = keyData;
            if ((ThisKey & Keys.Control) == Keys.Control)
                ThisKey ^= Keys.Control;

            if (ThisKey == Keys.Up)
                {
                    foreach (MM_OneLine_Element Elem in MovingObject)
                    MoveElement(Elem, new Point(0, -1));
                    this.Refresh();
                    return true;
                }
                else if (ThisKey == Keys.Down)
                {
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        //if (Elem is MM_OneLine_Descriptor == false || !MovingObject.Contains((Elem as MM_OneLine_Descriptor).ParentElement))
                        MoveElement(Elem, new Point(0, 1));
                    this.Refresh();
                    return true;
                }
                else if (ThisKey == Keys.Left)
                {
                    foreach (MM_OneLine_Element Elem in MovingObject)
                    MoveElement(Elem, new Point(-1, 0));
                    this.Refresh();
                    return true;
                }
                else if (ThisKey == Keys.Right)
                {
                    foreach (MM_OneLine_Element Elem in MovingObject)
                    MoveElement(Elem, new Point(1, 0));
                    this.Refresh();
                    return true;
                }
                else if (ThisKey == Keys.A)
                {
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        Elem.DescriptorArrow ^= true;
                    this.Refresh();
                    return true;
                }
                else if (ThisKey == Keys.Delete && MovingObject.Count > 0)
                {
                    StringBuilder sB = new StringBuilder();
                    sB.AppendLine("Are you sure you'd like to delete the following objects?");
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        sB.AppendLine("  " + Elem.GetType().Name + " " + Elem.Name);
                    if (MessageBox.Show(sB.ToString(), Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    {
                        DisplayParameters.HandleRemovedElements(MovingObject.ToArray());
                        MovingObject.Clear();
                        this.Refresh();
                    }
                    return true;
                }
                else
                    return base.ProcessCmdKey(ref msg, keyData);                
            
        }
        #endregion

        #region Menu bar interactions
        /// <summary>
        /// Handle the data source change
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDataSource_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            this.DisplayParameters.DataSource = e.ClickedItem.Text;
        }

        /// <summary>
        /// Handle the names click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNames_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.Names = btnNames.Checked;
        }

        /// <summary>
        /// Handle the percentages click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnPercent_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.Percentages = btnPercent.Checked;
        }

        /// <summary>
        /// Handle the MVA click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMVA_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.MVA = btnMVA.Checked;
        }

        /// <summary>
        /// Handle the MW click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMW_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.MW = btnMW.Checked;
        }

        /// <summary>
        /// Handle the MVAR click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMVAR_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.MVAR = btnMVAR.Checked;
        }

        /// <summary>
        /// Handle the other line click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnOtherLine_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.OtherLine = btnOtherLine.Checked;
        }

        /// <summary>
        /// Handle the voltage click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnVoltage_Click(object sender, EventArgs e)
        {
            this.DisplayParameters.Voltage = btnVoltage.Checked;
        }
        #endregion

        #region Rendering
        
        /// <summary>
        /// Paint the panel background by drawing the connectivity node lines
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_Paint(object sender, PaintEventArgs e)
        {
            //First, reposition the graphics 
            e.Graphics.TranslateTransform(pnlElements.AutoScrollPosition.X, pnlElements.AutoScrollPosition.Y);

            //Paint our header information            
            int HeaderHeight = 40;
            Rectangle HeaderRect = new Rectangle(0, 0, pnlElements.DisplayRectangle.Width, HeaderHeight);
            e.Graphics.FillRectangle(DisplayParameters.InformationMode ? Brushes.DarkGoldenrod : Brushes.DarkCyan, HeaderRect);            
                using (StringFormat sF = new StringFormat())
                {
                    sF.LineAlignment = StringAlignment.Center;

                    if (BaseElement == null)
                    { }
                    else if (BaseElement is MM_Substation)
                    {
                        MM_Substation BaseStation = BaseElement as MM_Substation;
                        using (Font LargeFont = new Font(FontFamily.GenericSerif, 12, FontStyle.Bold))
                            e.Graphics.DrawString(BaseStation.LongName + " (" + BaseStation.Name + ")", LargeFont, Brushes.LightGray, HeaderRect, MM_OneLine_Element.CenterFormat);

                        String LatLong = String.Format("Latitude: {0}\nLongitude: {1}", BaseStation.LatLong.Y, BaseStation.LatLong.X);
                        int LatLongWidth = (int)Math.Ceiling(e.Graphics.MeasureString(LatLong, SystemFonts.DefaultFont).Width);
                        e.Graphics.DrawString(LatLong, SystemFonts.DefaultFont, Brushes.LightGray, new Rectangle(pnlElements.DisplayRectangle.Width - LatLongWidth, 0, LatLongWidth, HeaderHeight), sF);
                    }
                    else
                    {
                        using (Font LargeFont = new Font(FontFamily.GenericSerif, 12, FontStyle.Bold))
                            e.Graphics.DrawString(ContingencyLongName, LargeFont, Brushes.LightGray, HeaderRect, MM_OneLine_Element.CenterFormat);

                        PointF MinPt = new PointF(float.NaN, float.NaN);
                        PointF MaxPt = new PointF(float.NaN, float.NaN);

                        foreach (MM_Element Elem in DisplayElements.Keys)
                            if (Elem.Substation != null && !float.IsNaN(Elem.Substation.LatLong.X))
                            {
                                if (float.IsNaN(MinPt.X) || MinPt.X > Elem.Substation.LatLong.X)
                                    MinPt.X = Elem.Substation.LatLong.X;
                                if (float.IsNaN(MinPt.Y) || MinPt.Y > Elem.Substation.LatLong.Y)
                                    MinPt.Y = Elem.Substation.LatLong.Y;
                                if (float.IsNaN(MaxPt.X) || MaxPt.X < Elem.Substation.LatLong.X)
                                    MaxPt.X = Elem.Substation.LatLong.X;
                                if (float.IsNaN(MaxPt.Y) || MaxPt.Y < Elem.Substation.LatLong.Y)
                                    MaxPt.Y = Elem.Substation.LatLong.Y;
                            }
                        String LatLong = String.Format("Latitude: {0:0.000} to {1:0.000}\nLongitude: {2:0.000} to {3:0.000}", MinPt.Y, MaxPt.Y, MinPt.X, MinPt.X);
                        int LatLongWidth = (int)Math.Ceiling(e.Graphics.MeasureString(LatLong, SystemFonts.DefaultFont).Width);
                        e.Graphics.DrawString(LatLong, SystemFonts.DefaultFont, Brushes.LightGray, new Rectangle(pnlElements.DisplayRectangle.Width - LatLongWidth, 0, LatLongWidth, HeaderHeight), sF);
                    }

                    String Export = string.Format("Exported on: {0}\nCIM file: {1}", ExportDate, LastCIM);
                    int ExportWidth = (int)Math.Ceiling(e.Graphics.MeasureString(Export, SystemFonts.DefaultFont).Width);
                    e.Graphics.DrawString(Export, SystemFonts.DefaultFont, Brushes.LightGray, new Rectangle(0, 0, ExportWidth, HeaderHeight), sF);
                }

            //Paint all of our nodes
            //e.Graphics.TranslateTransform(-pnlElements.AutoScrollPosition.X, -pnlElements.AutoScrollPosition.Y);
            if (DisplayParameters.ShowNodeToElementLines)
                foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                    Node.DrawConnectingLines(e.Graphics);
            //e.Graphics.TranslateTransform(pnlElements.AutoScrollPosition.X, pnlElements.AutoScrollPosition.Y);


            //Now, untranslate the image
            e.Graphics.TranslateTransform(-pnlElements.AutoScrollPosition.X, -pnlElements.AutoScrollPosition.Y);

            //Paint our descriptor arrows
            foreach (MM_OneLine_Descriptor Desc in Descriptors.Values)
                if (Desc != null)
                    if (Desc.ParentElement.DescriptorArrow || Desc.DescriptorArrow)
                        DrawArrow(Desc.Bounds, Desc.ParentElement.Bounds, e.Graphics, true);

            foreach (MM_OneLine_SecondaryDescriptor Desc in SecondaryDescriptors.Values)
                if (Desc != null)
                    if (Desc.ParentElement.DescriptorArrow || Desc.DescriptorArrow)
                        DrawArrow(Desc.Bounds, Desc.ParentElement.Bounds, e.Graphics, true);

            //If we're dragging, take care of it
            if (!DragRectangle.IsEmpty)
                e.Graphics.DrawLines(SystemPens.ActiveBorder, new Point[] { new Point(DragRectangle.Left, DragRectangle.Top), new Point(DragRectangle.Right, DragRectangle.Top), new Point(DragRectangle.Right, DragRectangle.Bottom), new Point(DragRectangle.Left, DragRectangle.Bottom), new Point(DragRectangle.Left, DragRectangle.Top)});
                
            //else
                //Also draw a rectangle around the selected element
                foreach (MM_OneLine_Element Elem in MovingObject)
                    if (pnlElements.Controls.Contains(Elem))
                    {
                        Rectangle OutBounds = new Rectangle(Elem.Left - 2, Elem.Top - 2, Elem.Width + 4, Elem.Height + 4);

                        //If our moving object has a text box, extend our box around it as well
                        if (Elem.Descriptor != null)
                        {
                            Control LabelToAdd = Elem.Descriptor;
                            OutBounds = Rectangle.FromLTRB(Math.Min(OutBounds.Left, LabelToAdd.Left - 2), Math.Min(OutBounds.Top, LabelToAdd.Top - 2), Math.Max(OutBounds.Right, LabelToAdd.Right + 4), Math.Max(OutBounds.Bottom, LabelToAdd.Bottom + 4));
                        }
                        if (Elem.SecondaryDescriptor!= null)
                        {
                            Control LabelToAdd = Elem.SecondaryDescriptor;
                            OutBounds = Rectangle.FromLTRB(Math.Min(OutBounds.Left, LabelToAdd.Left - 2), Math.Min(OutBounds.Top, LabelToAdd.Top - 2), Math.Max(OutBounds.Right, LabelToAdd.Right + 4), Math.Max(OutBounds.Bottom, LabelToAdd.Bottom + 4));
                        }
                        e.Graphics.DrawRectangle(SystemPens.ActiveBorder, OutBounds);
                    }
        }

        /// <summary>
        /// Return the center point of a rectangle
        /// </summary>
        /// <param name="InRect">The rectangle to center on</param>
        /// <returns></returns>
        private Point CenterPoint(Rectangle InRect)
        {
            return new Point(InRect.Left + (InRect.Width / 2), InRect.Top + (InRect.Height / 2));
        }

        #endregion

        #region Image saving
        /// <summary>
        /// Save the One-line image
        /// </summary>
        /// <param name="FileName"></param>
        public void SaveImage(string FileName)
        {
            if (System.IO.Path.GetExtension(FileName).ToLower() == ".png")
                OneLineImage.Save(FileName, System.Drawing.Imaging.ImageFormat.Png);
            else if (System.IO.Path.GetExtension(FileName).ToLower() == ".bmp")
                OneLineImage.Save(FileName, System.Drawing.Imaging.ImageFormat.Bmp);
            else                
                    OneLineImage.Save(FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
            //System.Diagnostics.Process.Start(FileName);
        }

        /// <summary>
        /// Retrieve an image of the one-line
        /// </summary>
        public Image OneLineImage
        {
            get
            {
                Bitmap OutBitmap = new Bitmap(pnlElements.DisplayRectangle.Width, pnlElements.DisplayRectangle.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                bool OldVal = DisplayParameters.DDLImportMode;
                DisplayParameters.DDLImportMode = false;

                //First, redraw all nodes.
                foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                    Node.RebuildPaths(pnlElements.AutoScrollPosition);

                using (Graphics g = Graphics.FromImage(OutBitmap))
                {
                    g.Clear(Color.Black);
                    DrawElements(g);
                }
                DisplayParameters.DDLImportMode = OldVal;
                return OutBitmap;
            }
        }
        /// <summary>
        /// Draw the elements onto a set graphics plane
        /// </summary>
        /// <param name="g"></param>
        private void DrawElements(Graphics g)
        {
            //Draw our header
            int HeaderHeight = 40;
            Rectangle HeaderRect = new Rectangle(0, 0, pnlElements.DisplayRectangle.Width, HeaderHeight);
            g.FillRectangle(Brushes.DarkCyan, HeaderRect);

            using (StringFormat sF = new StringFormat())
            {
                sF.LineAlignment = StringAlignment.Center;

                if (BaseElement == null)
                { }
                else if (BaseElement is MM_Substation)
                {
                    MM_Substation BaseStation = BaseElement as MM_Substation;
                    using (Font LargeFont = new Font(FontFamily.GenericSerif, 12, FontStyle.Bold))
                        g.DrawString(BaseStation.LongName + " (" + BaseStation.Name + ")", LargeFont, Brushes.LightGray, HeaderRect, MM_OneLine_Element.CenterFormat);

                    String LatLong = String.Format("Latitude: {0}\nLongitude: {1}", BaseStation.LatLong.Y, BaseStation.LatLong.X);
                    int LatLongWidth = (int)Math.Ceiling(g.MeasureString(LatLong, SystemFonts.DefaultFont).Width);
                    g.DrawString(LatLong, SystemFonts.DefaultFont, Brushes.LightGray, new Rectangle(pnlElements.DisplayRectangle.Width - LatLongWidth, 0, LatLongWidth, HeaderHeight), sF);
                }
                else
                {
                    using (Font LargeFont = new Font(FontFamily.GenericSerif, 12, FontStyle.Bold))
                        if (BaseElement.ElemType.Name == "Substation")
                            g.DrawString((BaseElement as MM_Substation).LongName + " (" + BaseElement.Name + ")", LargeFont, Brushes.LightGray, HeaderRect, MM_OneLine_Element.CenterFormat);
                    else
                        g.DrawString(ContingencyLongName, LargeFont, Brushes.LightGray, HeaderRect, MM_OneLine_Element.CenterFormat);

                    PointF MinPt = new PointF(float.NaN, float.NaN);
                    PointF MaxPt = new PointF(float.NaN, float.NaN);

                    foreach (MM_Element Elem in DisplayElements.Keys)
                        if (Elem.Substation != null && !float.IsNaN(Elem.Substation.LatLong.X))
                        {
                            if (float.IsNaN(MinPt.X) || MinPt.X > Elem.Substation.LatLong.X)
                                MinPt.X = Elem.Substation.LatLong.X;
                            if (float.IsNaN(MinPt.Y) || MinPt.Y > Elem.Substation.LatLong.Y)
                                MinPt.Y = Elem.Substation.LatLong.Y;
                            if (float.IsNaN(MaxPt.X) || MaxPt.X < Elem.Substation.LatLong.X)
                                MaxPt.X = Elem.Substation.LatLong.X;
                            if (float.IsNaN(MaxPt.Y) || MaxPt.Y < Elem.Substation.LatLong.Y)
                                MaxPt.Y = Elem.Substation.LatLong.Y;
                        }
                    String LatLong = String.Format("Latitude: {0:0.000} to {1:0.000}\nLongitude: {2:0.000} to {3:0.000}", MinPt.Y, MaxPt.Y, MinPt.X, MinPt.X);
                    int LatLongWidth = (int)Math.Ceiling(g.MeasureString(LatLong, SystemFonts.DefaultFont).Width);
                    g.DrawString(LatLong, SystemFonts.DefaultFont, Brushes.LightGray, new Rectangle(pnlElements.DisplayRectangle.Width - LatLongWidth, 0, LatLongWidth, HeaderHeight), sF);
                }

                String Export = string.Format("Exported on: {0}\nCIM file: {1}", ExportDate, LastCIM);
                int ExportWidth = (int)Math.Ceiling(g.MeasureString(Export, SystemFonts.DefaultFont).Width);
                g.DrawString(Export, SystemFonts.DefaultFont, Brushes.LightGray, new Rectangle(0, 0, ExportWidth, HeaderHeight), sF);
            }

     

            //Draw our node lines                
            foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                Node.DrawConnectingLines(g);
            
            //Now draw the elements
            foreach (Control ctl in pnlElements.Controls)
                if ((ctl is MM_OneLine_PokePoint == false) || (ctl as MM_OneLine_PokePoint).IsVisible)
            {
                Point Offset = new Point(ctl.Left - pnlElements.AutoScrollPosition.X, ctl.Top - pnlElements.AutoScrollPosition.Y);
                g.TranslateTransform(Offset.X, Offset.Y);
                if (ctl is MM_OneLine_Descriptor)
                    (ctl as MM_OneLine_Descriptor).DrawImage(g);
                else if (ctl is MM_OneLine_Element)
                    (ctl as MM_OneLine_Element).DrawImage(g);
                g.TranslateTransform(-Offset.X, -Offset.Y);
            }

            //Paint our descriptor arrows            
            g.TranslateTransform(-pnlElements.AutoScrollPosition.X, -pnlElements.AutoScrollPosition.Y);
            foreach (MM_OneLine_Descriptor Desc in Descriptors.Values)
                if (Desc.ParentElement.DescriptorArrow || Desc.DescriptorArrow)                    
                    DrawArrow(Desc.Bounds, Desc.ParentElement.Bounds, g, true);
            foreach (MM_OneLine_SecondaryDescriptor SDesc in SecondaryDescriptors.Values)
                if (SDesc.ParentElement.DescriptorArrow || SDesc.DescriptorArrow)
                    DrawArrow(SDesc.Bounds, SDesc.ParentElement.Bounds, g, true);
            g.TranslateTransform(pnlElements.AutoScrollPosition.X, pnlElements.AutoScrollPosition.Y);
        }

        /// <summary>
        /// Print a one-line image
        /// </summary>
        /// <param name="pDoc">The approved print settings</param>
        public void PrintImage(PrintDocument pDoc)
        {
            pDoc.PrintPage += pDoc_PrintPage;
            pDoc.EndPrint += pDoc_EndPrint;
            pDoc.Print();
        }

        /// <summary>
        /// Handle the end of printing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pDoc_EndPrint(object sender, PrintEventArgs e)
        {
            (sender as PrintDocument).Dispose();
        }

        private void pDoc_PrintPage(object sender, PrintPageEventArgs e)
        {
            //Start by transforming the page to the proper size
            e.Graphics.ScaleTransform((float)e.MarginBounds.Width / (float)pnlElements.DisplayRectangle.Width, (float)e.MarginBounds.Height / (float)pnlElements.DisplayRectangle.Height);
            DrawElements(e.Graphics);
            e.HasMorePages = false;            
        }



        #endregion

        #region XML saving
        /// <summary>
        /// Write out the one line XML
        /// </summary>
        /// <param name="xW">The XML text writer</param>
        public void WriteOneLineXml(XmlTextWriter xW)
        {
            WriteOneLineXml(xW, true);
        }

        /// <summary>
        /// Write out the one line XML
        /// </summary>
        /// <param name="xW">The XML text writer</param>
        /// <param name="WriteStartEnd">Whether to write the start and end</param>
        public void WriteOneLineXml(XmlTextWriter xW, bool WriteStartEnd)
        {
            pnlElements.AutoScrollPosition = Point.Empty;
            Application.DoEvents();

            if (WriteStartEnd)
            {
                xW.WriteStartDocument(true);
                xW.Formatting = Formatting.Indented;
            }
            xW.WriteStartElement("One_Line");
            MM_Serializable.WriteAttributes(xW, BaseElement, "BaseElement");
            xW.WriteAttributeString("ExportDate", XmlConvert.ToString(ExportDate, XmlDateTimeSerializationMode.Unspecified));
            xW.WriteAttributeString("LastCIM", LastCIM);
            xW.WriteAttributeString("Font", new FontConverter().ConvertToString(this.Font));

            //Write out the elements            
            xW.WriteStartElement("Elements");
            foreach (MM_OneLine_Element Elem in DisplayElements.Values)
                if (Elem is MM_OneLine_Descriptor == false && Elem is MM_OneLine_SecondaryDescriptor == false && Elem is MM_OneLine_PokePoint == false)
                    Elem.WriteXml(xW);
            xW.WriteEndElement();

            //Write out the nodes
            xW.WriteStartElement("Nodes");
            foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                Node.WriteXml(xW);
            xW.WriteEndElement();

            //Write out the unlinked elements
            xW.WriteStartElement("Unlinked_Elements");
            foreach (MM_OneLine_UnlinkedElement Elem in UnlinkedElements.Keys)
                Elem.WriteXml(xW);
            xW.WriteEndElement();
            xW.WriteEndElement();
            if (WriteStartEnd)
            {
                xW.WriteEndDocument();
                xW.Flush();
            }
        }
    #endregion

        /// <summary>
        /// Locate a poke point at a specified location. If not found, create it.
        /// </summary>
        /// <param name="Bounds"></param>
        /// <param name="BaseNode"></param>
        /// <param name="DisplayParams"></param>
        /// <param name="Elem"></param>
        /// <param name="Orientation"></param>
        /// <param name="IsJumper"></param>
        /// <param name="IsVisible"></param>
        /// <returns></returns>                
        public MM_OneLine_PokePoint LocatePoke(Rectangle Bounds, MM_OneLine_Node BaseNode, MM_OneLine_Display DisplayParams, MM_Element Elem, MM_OneLine_Element.enumOrientations Orientation, bool IsJumper, bool IsVisible)
        {
            Rectangle NewBounds = new Rectangle(Bounds.X + pnlElements.AutoScrollPosition.X, Bounds.Y + pnlElements.AutoScrollPosition.Y, Bounds.Width, Bounds.Height);
            if (PokePoints.ContainsKey(NewBounds.Location))
                return PokePoints[NewBounds.Location];
            else
            {
                MM_OneLine_PokePoint NewPoint = new MM_OneLine_PokePoint(Elem, BaseNode, DisplayParams, Orientation, IsJumper, IsVisible);
                NewPoint.Bounds = NewBounds;                
                PokePoints.Add(NewBounds.Location, NewPoint);
                NewPoint.BringToFront();
                return NewPoint;
            }
        }




        /// <summary>
        /// Handle the aligning of data
        /// </summary>
        /// <param name="Orientation">The selected orientation</param>
        public void AlignSelection(string Orientation)
        {
            if (Orientation == "&Undo last alignment")
                RestoreBounds();
            else if (Orientation == "&Rotate element(s)")
            {
                foreach (MM_OneLine_Element Elem in MovingObject)
                    if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Left)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Up;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Up)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Right;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Right)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Down;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Down)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Left;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Horizontal)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Vertical;
                    else if (Elem.Orientation == MM_OneLine_Element.enumOrientations.Vertical)
                        Elem.Orientation = MM_OneLine_Element.enumOrientations.Horizontal;
            }
            else if (Orientation == "&Snap to grid")
            {
                StoreBounds();
                int GridSize = 4;
                foreach (Control ctl in pnlElements.Controls)
                    if (ctl is MM_OneLine_Descriptor == false)
                    {
                        Point CenterDelta = MM_OneLine_Element.CenterRect(ctl.Bounds);
                        MoveElement(ctl, new Point(-(CenterDelta.X % GridSize), -(CenterDelta.Y % GridSize)));
                    }
            }
            else if (Orientation == "&Merge poke points")
                MergePokePoints();
            else
            {
                StoreBounds();
                for (int a = 1; a < MovingObject.Count; a++)
                {
                    if (Orientation == "Left")
                        MovingObject[a].Left = MovingObject[0].Left;
                    else if (Orientation == "Right")
                        MovingObject[a].Width = MovingObject[0].Right - MovingObject[a].Left;
                    else if (Orientation == "Top")
                        MovingObject[a].Top = MovingObject[0].Top;
                    else if (Orientation == "Bottom")
                        MovingObject[a].Height = MovingObject[0].Bottom - MovingObject[a].Top;
                    else if (Orientation.StartsWith("Horizontal"))
                    {
                        Point CenterPt = MM_OneLine_Element.CenterRect(MovingObject[0].Bounds);
                        Point ThisCenter = MM_OneLine_Element.CenterRect(MovingObject[a].Bounds);
                        MovingObject[a].Top -= ThisCenter.Y - CenterPt.Y;
                    }

                    else if (Orientation.StartsWith("Vertical"))
                    {
                        Point CenterPt = MM_OneLine_Element.CenterRect(MovingObject[0].Bounds);
                        Point ThisCenter = MM_OneLine_Element.CenterRect(MovingObject[a].Bounds);
                        MovingObject[a].Left += CenterPt.X - ThisCenter.X;
                    }

                    else
                        throw new InvalidOperationException("Unknown orientation: " + Orientation);
                    MoveElement(MovingObject[a], Point.Empty);
                }

                //Handle the labels if requested
                if (Orientation == "Horizontal + Labels Below Elements")
                {
                    int BottomMost = 0;
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem is MM_OneLine_Descriptor == false && Elem.Visible)                        
                            BottomMost = Math.Max(Elem.Bottom, BottomMost);

                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem.Visible)
                    {
                        Point CenterElem = MM_OneLine_Element.CenterRect(Elem.Bounds);
                        if (Elem.Descriptor != null)
                            Elem.Descriptor.Location = new Point(CenterElem.X - MM_OneLine_Element.CenterRect(Elem.Descriptor.DisplayRectangle).X, BottomMost + 2);
                        if (Elem.SecondaryDescriptor != null)
                            Elem.SecondaryDescriptor.Location = new Point(CenterElem.X - MM_OneLine_Element.CenterRect(Elem.SecondaryDescriptor.DisplayRectangle).X, BottomMost + 2);
                    }
                }
                else if (Orientation == "Horizontal + Labels Above Elements")
                {
                    int TopMost = int.MaxValue;
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem is MM_OneLine_Descriptor == false && Elem.Visible)                        
                            TopMost = Math.Min(Elem.Top, TopMost);

                    foreach (MM_OneLine_Element Elem in MovingObject)
                    if (Elem.Visible)
                    {
                        Point CenterElem = MM_OneLine_Element.CenterRect(Elem.Bounds);
                        if (Elem.Descriptor != null)
                            Elem.Descriptor.Location = new Point(CenterElem.X - MM_OneLine_Element.CenterRect(Elem.Descriptor.DisplayRectangle).X, TopMost - Elem.Descriptor.Height - 7);
                        if (Elem.SecondaryDescriptor != null)
                            Elem.SecondaryDescriptor.Location = new Point(CenterElem.X - MM_OneLine_Element.CenterRect(Elem.SecondaryDescriptor.DisplayRectangle).X, TopMost - Elem.SecondaryDescriptor.Height - 7);
                    }
                }
                else if (Orientation == "Vertical + Labels to Right of Elements")
                {
                    int RightMost = 0;
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem is MM_OneLine_Descriptor == false && Elem.Visible)                        
                            RightMost = Math.Max(Elem.Right, RightMost);

                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem.Visible)
                    {
                        Point CenterElem = MM_OneLine_Element.CenterRect(Elem.Bounds);
                        if (Elem.Descriptor != null)
                            Elem.Descriptor.Location = new Point(RightMost + 2, CenterElem.Y - MM_OneLine_Element.CenterRect(Elem.Descriptor.DisplayRectangle).Y);
                        if (Elem.SecondaryDescriptor != null)
                            Elem.SecondaryDescriptor.Location = new Point(RightMost + 2, CenterElem.Y - MM_OneLine_Element.CenterRect(Elem.SecondaryDescriptor.DisplayRectangle).Y);
                    }
                }
                else if (Orientation == "Vertical + Labels to Left of Elements")
                {
                    int LeftMost = int.MaxValue;
                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem is MM_OneLine_Descriptor == false && Elem.Visible)                        
                            LeftMost = Math.Min(Elem.Left, LeftMost);

                    foreach (MM_OneLine_Element Elem in MovingObject)
                        if (Elem.Visible)
                        {
                            Point CenterElem = MM_OneLine_Element.CenterRect(Elem.Bounds);
                            if (Elem.Descriptor != null)
                                Elem.Descriptor.Location = new Point(LeftMost - Elem.Descriptor.Width - 2, CenterElem.Y - MM_OneLine_Element.CenterRect(Elem.Descriptor.DisplayRectangle).Y);
                            if (Elem.SecondaryDescriptor != null)
                                Elem.SecondaryDescriptor.Location = new Point(LeftMost - Elem.SecondaryDescriptor.Width - 2, CenterElem.Y - MM_OneLine_Element.CenterRect(Elem.SecondaryDescriptor.DisplayRectangle).Y);
                        }
                }


                foreach (MM_OneLine_Node Node in DisplayNodes.Values)
                    Node.RebuildPaths(pnlElements.AutoScrollPosition);

                
                
                foreach (MM_OneLine_Node NOB in DisplayNodes.Values)
                    NOB.RebuildPaths(pnlElements.AutoScrollPosition);                
            }
            pnlElements.Refresh();
        }

        /// <summary>
        /// Handle a click in the element panel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseClick(object sender, MouseEventArgs e)
        {
            if (PanelClicked != null)
                PanelClicked(sender, new MouseEventArgs(e.Button, e.Clicks, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, e.Delta));
            if ((Control.ModifierKeys & Keys.Shift) != Keys.Shift)
            {
                DragRectangle = Rectangle.Empty;
                MovingObject.Clear();
            }
        }

        /// <summary>
        /// Handle the mouse movement by reporting our updated position
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseMove(object sender, MouseEventArgs e)
        {
            if (PanelMouseMove != null)
                PanelMouseMove(sender, new MouseEventArgs(e.Button, e.Clicks, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, e.Delta));
            if (e.Button == MouseButtons.Left && (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
                if (DragRectangle.IsEmpty)
                    DragRectangle = new Rectangle(e.Location, Size.Empty);
                else
                {
                    DragRectangle.Width = e.Location.X - DragRectangle.Left;
                    DragRectangle.Height = e.Location.Y - DragRectangle.Top;
                    pnlElements.Refresh();
                }
        }
    
        /// <summary>
        /// Handle the mouse up by selecting all requested elements
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (!DragRectangle.IsEmpty)
            {

                //When the mouse is up, select all elements
                //MovingObject.Clear();

                DragRectangle = Rectangle.FromLTRB(Math.Min(DragRectangle.Left, DragRectangle.Right), Math.Min(DragRectangle.Top, DragRectangle.Bottom), Math.Max(DragRectangle.Left, DragRectangle.Right), Math.Max(DragRectangle.Top, DragRectangle.Bottom));

                //First add all the non-descriptors)
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem is MM_OneLine_Node)
                    {
                        if (Elem.Left >= DragRectangle.Left && Elem.Right <= DragRectangle.Right && Elem.Top >= DragRectangle.Top && Elem.Bottom <= DragRectangle.Bottom )
                            MovingObject.Add(Elem);
                    }
                    else if (Elem is MM_OneLine_Descriptor == false)                        
                        if (DragRectangle.IntersectsWith(Elem.Bounds))
                            MovingObject.Add(Elem);

                //Now the descriptors
                foreach (MM_OneLine_Element Elem in pnlElements.Controls)
                    if (Elem is MM_OneLine_Descriptor)
                        if (DragRectangle.IntersectsWith(Elem.Bounds) && !MovingObject.Contains((Elem as MM_OneLine_Descriptor).ParentElement))
                            MovingObject.Add(Elem);

                DragRectangle = Rectangle.Empty;
                this.Refresh();
            }
        }

        private void pnlElements_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
                if ((Control.ModifierKeys & Keys.Shift) == Keys.None)                    
                {
                    MovingObject.Clear(); 
                    this.Refresh();
                }

        }


        /// <summary>
        /// Handle the double-clicking on the current location
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pnlElements_MouseDoubleClick(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (PanelDoubleClicked != null)
                PanelDoubleClicked(sender, new MouseEventArgs(e.Button, e.Clicks, e.X - pnlElements.AutoScrollPosition.X, e.Y - pnlElements.AutoScrollPosition.Y, e.Delta));

        }        


        /// <summary>
        /// Merge the selected poke points together
        /// </summary>
        public void MergePokePoints()
        {

            //First, make sure everything is set
            bool BusSelected = false;
            MM_OneLine_Node BaseNode = null;
            if (MovingObject.Count < 2)
                MessageBox.Show("Unable to merge poke points: Insufficient elements.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            else
                foreach (MM_OneLine_Element Elem in MovingObject)
                    if (Elem is MM_OneLine_Node)
                        BusSelected = (BaseNode = Elem as MM_OneLine_Node) != null;
                    else if (Elem is MM_OneLine_PokePoint && Elem is MM_OneLine_PricingVector == false)
                        if (BaseNode == null)
                            BaseNode = (Elem as MM_OneLine_PokePoint).BaseNode;
                        else if ((Elem as MM_OneLine_PokePoint).BaseNode != BaseNode)
                        {
                            MessageBox.Show("Unable to merge poke points: More than one connectivity node is currently selected.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                            return;
                        }

            MM_OneLine_PokePoint MasterPoke = null;
            List<MM_OneLine_Element> ToRemove = new List<MM_OneLine_Element>();
            List<MM_OneLine_Node> ToUpdate = new List<MM_OneLine_Node>();
            
            //Go through every selected element. Remove the unlinked ones, and assign the other ones to the first selected one
            for (int a = 0; a < MovingObject.Count; a++)
                if (MovingObject[a] is MM_OneLine_UnlinkedElement)
                    ToRemove.Add(MovingObject[a]);
                else if (MovingObject[a] is MM_OneLine_PokePoint && !(MovingObject[a] is MM_OneLine_PricingVector))
                {
                    MM_OneLine_PokePoint Poke = MovingObject[a] as MM_OneLine_PokePoint;
                    if (MasterPoke == null)
                    {
                        MasterPoke = Poke;
                        MasterPoke.Bounds = MovingObject[0].Bounds;
                        ToUpdate.Add(MasterPoke.BaseNode);
                    }
                    else
                    {
                        ToRemove.Add(Poke);
                        if (Poke.Descriptor != null)
                        {
                            MasterPoke.Descriptor = Poke.Descriptor;
                            MasterPoke.Descriptor.ParentElement = MasterPoke;
                        }
                        if (Poke.SecondaryDescriptor != null)
                        {
                            MasterPoke.SecondaryDescriptor = Poke.SecondaryDescriptor;
                            MasterPoke.SecondaryDescriptor.ParentElement = MasterPoke;
                        }
                        Poke.Descriptor = null;
                        Poke.SecondaryDescriptor = null;
                        Poke.BaseNode = MasterPoke.BaseNode;
                        if (Poke.BaseNode != null)
                        {
                            if (!ToUpdate.Contains(Poke.BaseNode))
                                ToUpdate.Add(Poke.BaseNode);
                            foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in Poke.BaseNode.ConnectionPoints)
                                for (int b = 0; b < kvp.Value.Length; b++)
                                    if (kvp.Value[b] == Poke)
                                        kvp.Value[b] = MasterPoke;
                        }
                       // Poke.BaseNode = null;
                    }
                }

            //Now handle the updates
            DisplayParameters.HandleRemovedElements(ToRemove.ToArray());
            Application.DoEvents();
            foreach (MM_OneLine_Node Node in ToUpdate)
                Node.RebuildPaths(pnlElements.AutoScrollPosition);
            pnlElements.Refresh();
        }
    }
}
